-
Notifications
You must be signed in to change notification settings - Fork 141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement execution traces for the FVM #412
Conversation
883df8c
to
2c7cca2
Compare
fvm/src/call_manager/default.rs
Outdated
ExecutionError::Syscall(s) => Receipt { | ||
exit_code: match s.1.try_into() { | ||
Ok(e) => e, | ||
_ => ErrPlaceholder, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm. Any chance we could change the format? We're intentionally distinguishing between syscall errors and exit codes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically, receipts are really for recording results on-chain, but aren't the best for debugging. In this case, it's also causing us to drop the error message.
fvm/src/call_manager/default.rs
Outdated
@@ -110,12 +138,19 @@ where | |||
res | |||
} | |||
|
|||
fn finish(mut self) -> (i64, Backtrace, Self::Machine) { | |||
fn finish(mut self) -> (FinishResult, Self::Machine) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: FinishResult
sounds like a specialized Result
type. Can we change this type name to FinishRet
, parallel with ApplyRet
?
fvm/src/call_manager/mod.rs
Outdated
@@ -140,3 +146,9 @@ impl InvocationResult { | |||
} | |||
} | |||
} | |||
|
|||
pub struct FinishResult { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: docs.
fvm/src/call_manager/mod.rs
Outdated
@@ -140,3 +146,9 @@ impl InvocationResult { | |||
} | |||
} | |||
} | |||
|
|||
pub struct FinishResult { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe just make the type pub(crate)
instead of its fields? It's a little bit useless to have a public method-less struct with all pub(crate)
fields.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd just make it all public.
fvm/src/executor/default.rs
Outdated
@@ -149,12 +153,15 @@ where | |||
}; | |||
|
|||
match apply_kind { | |||
ApplyKind::Explicit => self.finish_message(msg, receipt, failure_info, gas_cost), | |||
ApplyKind::Explicit => { | |||
self.finish_message(msg, receipt, failure_info, gas_cost, exec_trace) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, this was confusing to me. I didn't expect finish_message
to operate on the exec_trace
, so I didn't understand why we passed it in. And indeed it doesn't.
Did you consider:
.map(|apply_ret| {
apply_ret.exec_trace = exec_trace;
apply_ret
})
It's a few more lines of code but it's less surprising (for me). Alternatively we could use a one-liner:
.inspect(|apply_ret| { apply_ret.exec_trace = exec_trace })
But it requires us to enable an experimental API: rust-lang/rust#91345.
fvm/src/call_manager/default.rs
Outdated
self.exec_trace = Some(vec![call_event]); | ||
} else { | ||
self.exec_trace.as_mut().unwrap().push(call_event); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be written as
if let Some(ref mut exec_trace) = self.exec_trace {
exec_trace.push(call_event);
} else {
self.exec_trace = Some(vec![call_event]);
}
fvm/src/call_manager/default.rs
Outdated
if self.machine.config().debug { | ||
self.exec_trace | ||
.as_mut() | ||
.unwrap() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be at least an .expect
explaining why this is already checked
fvm/src/call_manager/default.rs
Outdated
if self.exec_trace.is_none() { | ||
self.exec_trace = Some(vec![call_event]); | ||
} else { | ||
self.exec_trace.as_mut().unwrap().push(call_event); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given that this is just a vector, we can probably drop the option entirely and just start out with an empty vector.
But I also feel compelled to one-up @dignifiedquire:
if self.exec_trace.is_none() { | |
self.exec_trace = Some(vec![call_event]); | |
} else { | |
self.exec_trace.as_mut().unwrap().push(call_event); | |
} | |
self.exec_trace.get_or_insert_with(Vec::new).push(call_event); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👌
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Y'all and this entire language are outta control. But that's cool to see.
fvm/src/call_manager/mod.rs
Outdated
@@ -140,3 +146,9 @@ impl InvocationResult { | |||
} | |||
} | |||
} | |||
|
|||
pub struct FinishResult { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd just make it all public.
9fae9b5
to
2ccd494
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly LGTM with some comments.
(Ok((result, gas_used, backtrace)), machine) | ||
let (res, machine) = cm.finish(); | ||
( | ||
Ok((result, res.gas_used, res.backtrace, res.exec_trace)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok((result, res.gas_used, res.backtrace, res.exec_trace)), | |
Ok(((result, res), machine)), |
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Eh, but we use gas_used
so many times, and they're not suuuper related concepts, and I don't like it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can destructure this on the outside. I'm just suggesting a way to slightly reduce the amount of code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(but it doesn't really matter)
fvm/src/kernel/error.rs
Outdated
impl Clone for ExecutionError { | ||
fn clone(&self) -> Self { | ||
match self { | ||
OutOfGas => OutOfGas, | ||
Syscall(v) => Syscall(v.clone()), | ||
Fatal(v) => Fatal(anyhow::Error::msg(v.to_string())), | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm... this isn't really a clone. I'd handle these cases separately:
- fatal/out-of-gas: nothing? We can just cut the trace short because it doesn't really matter.
- Syscall error -> push that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Basically, record Result<InvocationResult, SyscallError>
instead of Result<InvocationResult, ExecutionError>
.
2ccd494
to
9b9280e
Compare
9b9280e
to
39a7575
Compare
@@ -125,7 +125,16 @@ where | |||
self.call_stack_depth -= 1; | |||
|
|||
if self.machine.context().tracing { | |||
self.exec_trace.push(ExecutionEvent::Return(result.clone())); | |||
self.exec_trace.push(ExecutionEvent::Return(match result { | |||
Err(ref e) => Err(match e { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This combination of ref
s and clone
s appeased the checker, but I feel like there's something better that can be done?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, you can use &result
and remove the ref e
and friends, but you still need to clone. On the other hand, this will only happen when tracing is enabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
(Ok((result, gas_used, backtrace)), machine) | ||
let (res, machine) = cm.finish(); | ||
( | ||
Ok((result, res.gas_used, res.backtrace, res.exec_trace)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can destructure this on the outside. I'm just suggesting a way to slightly reduce the amount of code.
(Ok((result, gas_used, backtrace)), machine) | ||
let (res, machine) = cm.finish(); | ||
( | ||
Ok((result, res.gas_used, res.backtrace, res.exec_trace)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(but it doesn't really matter)
@@ -125,7 +125,16 @@ where | |||
self.call_stack_depth -= 1; | |||
|
|||
if self.machine.context().tracing { | |||
self.exec_trace.push(ExecutionEvent::Return(result.clone())); | |||
self.exec_trace.push(ExecutionEvent::Return(match result { | |||
Err(ref e) => Err(match e { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
self.exec_trace.push(ExecutionEvent::Return(match result { | ||
Err(ref e) => Err(match e { | ||
ExecutionError::OutOfGas => { | ||
SyscallError::new(ErrorNumber::Forbidden, "out of gas") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these syscall errors numbers the correct ones to use here?
Resolves #318
This gets us most of the way to matching Lotus execution traces -- fine gas tracing isn't implemented yet, but it will be impossible to meet that level of detail in M1 anyway.
The ExecutionTrace is created and populated by the call manager, which returns it on
finish
.